#ifndef BOSS_Afterburner_Functions_PrintTable_H
#define BOSS_Afterburner_Functions_PrintTable_H

#include "TString.h"
#include <fstream>
#include <list>
#include <sstream>
#include <string>
#include <vector>

class Table;
class EndTableRow
{};

struct TableColumn
{
  friend Table;
  enum EAlign
  {
    Left,
    Right,
    Internal
  };
  size_t Width;
  EAlign Align;
};

struct TableRow
{
  friend Table;
  Long64_t    SortValue;
  std::string Name;
  void        SetCells(const std::vector<std::string>& v);

  template <typename T>
  inline TableRow& operator<<(T val);

  void Print() const;
  void Print(const std::vector<TableColumn>& format) const;
  void Write(std::ofstream&, const char* delim = "\t") const;
  void WriteLaTeX(std::ofstream&) const;
  void WriteLaTeXBold(std::ofstream&) const;
  void WriteLaTeX(std::ofstream&, const std::vector<TableColumn>& format) const;

  const std::string& operator[](size_t i) const;
  const std::string& LastCell() const { fCells.back(); }
  size_t             GetEntries() const { return fCells.size(); }
  Bool_t             IsEmpty() const { return !GetEntries(); }

  static EndTableRow endrow;

private:
  std::vector<std::string> fCells;

  template <typename T>
  inline void AddCell(T val);
};

class Table
{
public:
  Table(size_t ncolumns = 0) : fColumns(ncolumns) { fHeader.SortValue = 0; }

  void Clean();
  void Sort();
  void SetHeader(std::vector<std::string> v);
  void SetAlign(std::vector<TableColumn::EAlign> v);
  void SetAlign(size_t i, TableColumn::EAlign align);
  void SetLastSortValue(Long64_t val);
  void SetLastName(const std::string& name);
  Bool_t ContainsEmptyLines() const;

  void Print() const;
  void PrintLine() const;
  void Write(std::string filename, const char* delim = "\t") const;
  void WriteLaTeX(std::string filename) const;

  template <typename T>
  inline Table& operator<<(T val);

  const TableRow& operator[](size_t i) const;
  const TableRow& operator[](const std::string& name) const;

  const std::list<TableRow>& GetRows() const { return fRows; }

private:
  TableRow                 fHeader;
  std::list<TableRow>      fRows;
  std::vector<TableColumn> fColumns;

  void SetWidth(size_t index, const std::string& width);
  void SetWidths(const TableRow& row);
  void AddRow(const TableRow& row);
  void AddRow();
};

template <typename T>
inline void TableRow::AddCell(T val)
{
  std::stringstream ss;
  ss << val;
  AddCell(ss.str());
}

template <>
inline void TableRow::AddCell<std::string>(std::string val)
{
  fCells.push_back(val);
}

template <typename T>
inline TableRow& TableRow::operator<<(T val)
{
  AddCell(val);
  return *this;
}

template <>
inline TableRow& TableRow::operator<<(EndTableRow val)
{
  return *this;
}

template <typename T>
inline Table& Table::operator<<(T val)
{
  if(fRows.size())
  {
    fRows.back() << val;
    SetWidths(fRows.back());
  }
  else
  {
    TableRow row;
    row << val;
    AddRow(row);
  }
  return *this;
};

template <>
inline Table& Table::operator<<(EndTableRow val)
{
  AddRow();
  return *this;
};

#endif